package ch.uzh.ifi.sonar.analyzers;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.eclipse.swt.widgets.Text;

import ch.uzh.ifi.sonar.qacpp.MetFileAnalyzer;
import ch.uzh.ifi.sonar.qacpp.QacppStarter;
import ch.uzh.ifi.sonar.qacpp.StreamAnalysisResultWriter;
import ch.uzh.ifi.sonar.qacpp.StreamsSummaryFileWriter;
import ch.uzh.ifi.sonar.util.FileTraverser;
import ch.uzh.ifi.sonar.util.SonarConstant;

/**
 * StreamAnalyzer: wrapper for the operations for a stream.
 * 
 * @author: Yi Guo
 * */
public class StreamAnalyzer implements IAnalyzer {

	public static final String QACPP_OUT_FOLDER = "qacpp_output";
	public static final String FILE_LIST_LST = "filelist.lst";
	public static final String SETTING_FILE_PATH = "D:\\sonar_workspace\\sonar_working_settings\\settings.via";
	public static final String STREAM_SUM_FOLDER = "D:\\sonar_workspace\\sonar_working_output\\streamAnalysis_result";

	public static final String STREAM__GENERAL_SUM_FOLDER = "D:\\sonar_workspace\\sonar_working_output\\summary_result";
	public static final String GENERAL_SUMMARY_FILE_NAME = "summary.csv";
	public static final String DETAILED_SUMMARY_FILE_NAME = "detail_summary.csv";
	public static final String CSV_SEP = SonarConstant.CSV_SEP;

	private Map<Object, List<?>> detailedMetrics;
	private List<?> summarizedMetrics;
	private Map<String, Map<String, Long>> sourceMetricMap;

	private final String streamName;
	private final String streamSrcPath;
	private final Text console;

	public StreamAnalyzer(String stream, String srcPath, Text console) {
		this.streamName = stream;
		this.streamSrcPath = srcPath;
		this.console = console;
		summarizedMetrics = new ArrayList<Long>();
		detailedMetrics = new HashMap<Object, List<?>>();
	}

	@Override
	public void persistSummary() {		
		try {
			StreamsSummaryFileWriter sumWriter = new StreamsSummaryFileWriter(STREAM__GENERAL_SUM_FOLDER
					+ File.separatorChar + GENERAL_SUMMARY_FILE_NAME);
			sumWriter.writeHeader();
			File streamsFolder = new File(STREAM_SUM_FOLDER);
			File[] streamCSVList = streamsFolder
					.listFiles(new FilenameFilter() {
						@Override
						public boolean accept(File file, String fileName) {
							return fileName.endsWith(".csv") && fileName.startsWith("prjIter");
						}
					});
			for (File streamCSV : streamCSVList) {
				sumWriter.writeContent(streamCSV);
			}
			sumWriter.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	@Override
	public void persistDetail() {

	}

	public void analyze(String streamName) throws IOException {
		File outputDir = this.getQACPPOutputDir();
		// File outputDir = new File("N:\\CCFS\\qacpp_output");
		FileTraverser ft = new FileTraverser();
		long beforeListTime = System.currentTimeMillis() / 1000;
		log(this.console, "[Start listing .met Files:]" + Long.toString(beforeListTime));
		List<File> mets = ft.getAllMetFiles(outputDir);
		long afterListTime = System.currentTimeMillis() / 1000;
		log(this.console, "[Finish listing .met Files]: "
				+ Long.toString(afterListTime - beforeListTime));
		String[] metFiles = new String[mets.size()];
		for (int i = 0; i < mets.size(); i++) {
			metFiles[i] = mets.get(i).getAbsolutePath();
		}
		/*
		 * if there is no .met file found, that we should start qacpp.exe to
		 * analyze this stream
		 */
		if (metFiles == null || metFiles.length == 0) {
			this.launchQacpp();
		} else {
			/* analyze all met files within the qacpp output folder */
			StreamAnalysisResultWriter streamWriter = null;
			try {
				streamWriter = new StreamAnalysisResultWriter(
						new BufferedWriter(
								new FileWriter(STREAM_SUM_FOLDER
										+ File.separatorChar + this.streamName
										+ ".csv")));
				MetFileAnalyzer ma;
				streamWriter.writeHeader();
				long beforeTime, afterTime;
				for (String metPath : metFiles) {
					beforeTime = System.currentTimeMillis() / 1000;
					File metFile = new File(metPath);
					log(this.console, "[Start analyzing metFile: ]" + metPath + "at "
							+ Long.toString(beforeTime));
					ma = new MetFileAnalyzer(metFile);
					streamWriter.writeContent(ma);
					afterTime = System.currentTimeMillis() / 1000;
					log(this.console, "[Finish Analyzing metFile: ]"
							+ Long.toString(afterTime - beforeTime)
							+ " sec for " + metPath);
				}
				streamWriter.close();
			} catch (IOException e) {
				e.printStackTrace();
			} finally {
				if (streamWriter != null)
					streamWriter.close();
			}

		}
	}

	private void launchQacpp() {
		// start the process of QACPP.exe
		// TODO: Launch the GUI progress bar
		QacppStarter starter = new QacppStarter(SETTING_FILE_PATH,
				FILE_LIST_LST);
		Process p = starter.executeProcessByStream();
		if (p != null) {
			BufferedReader stdReader = new BufferedReader(
					new InputStreamReader(p.getInputStream()));
			String stdLine;
			try {
				while ((stdLine = stdReader.readLine()) != null) {
					log(this.console, stdLine);
				}
			} catch (IOException e) {
				log(this.console, "Error:" + e.getMessage());
				e.printStackTrace();
			}
		}
	}

	@Override
	public void analyze() {
		File outputDir = this.getQACPPOutputDir();
		// File outputDir = new File("N:\\CCFS\\qacpp_output");

		FileTraverser ft = new FileTraverser();
		long beforeListTime = System.currentTimeMillis() / 1000;
		log(this.console, "[Start listing all met Files:]" + Long.toString(beforeListTime));
		List<File> mets = ft.getAllMetFiles(outputDir);
		long afterListTime = System.currentTimeMillis() / 1000;
		log(this.console, "[Spend on listing all met Files]: "
				+ Long.toString(afterListTime - beforeListTime));
		String[] metFiles = new String[mets.size()];
		for (int i = 0; i < mets.size(); i++) {
			metFiles[i] = mets.get(i).getAbsolutePath();
		}

		if (metFiles == null || metFiles.length == 0) {
			// start the process of QACPP.exe
			// TODO: Launch the GUI progress bar
			QacppStarter starter = new QacppStarter(SETTING_FILE_PATH,
					FILE_LIST_LST);
			Process p = starter.executeProcessByStream();
			if (p != null) {
				BufferedReader stdReader = new BufferedReader(
						new InputStreamReader(p.getInputStream()));
				String stdLine;
				try {
					while ((stdLine = stdReader.readLine()) != null) {
						log(this.console, stdLine);
					}
				} catch (IOException e) {
					log(this.console, e.getMessage());
					e.printStackTrace();
				}
			}
		} else {
			sourceMetricMap = new HashMap<String, Map<String, Long>>();
			Map<String, Long> metricMap;
			long beforeTime, afterTime;
			for (String metPath : metFiles) {
				beforeTime = System.currentTimeMillis() / 1000;
				File metFile = new File(metPath);
				log(this.console, "[Start analyzing metFile: ]" + metPath + "at "
						+ Long.toString(beforeTime));
				MetFileAnalyzer ma = new MetFileAnalyzer(metFile);
				metricMap = new HashMap<String, Long>();
				metricMap.put("STCYC", ma.getSTCYC());
				metricMap.put("STDIT", ma.getSTDIT());
				metricMap.put("STGTO", ma.getSTGTO());
				metricMap.put("STLCM", ma.getSTLCM());
				metricMap.put("STLIN", ma.getSTLIN());
				metricMap.put("STMIF", ma.getSTMIF());
				metricMap.put("STMTH", ma.getSTMTH());
				metricMap.put("STNOC", ma.getSTNOC());
				metricMap.put("STNOP", ma.getSTNOP());
				metricMap.put("STOPT", ma.getSTOPT());
				metricMap.put("STSUB", ma.getSTSUB());
				metricMap.put("STTLN", ma.getSTTLN());
				metricMap.put("STVAR", ma.getSTVAR());
				metricMap.put("STWMC", ma.getSTWMC());
				metricMap.put("STXLN", ma.getSTXLN());

				String sourceFileRelativePath = metPath.substring(0,
						metPath.lastIndexOf(".met"));
				// FIXME: Magic number 10 and bug prone statement;
				// sourceMetricMap.put(sourceFileRelativePath.substring(sourceFileRelativePath.indexOf("\\",
				// 10)+1), metricMap);
				afterTime = System.currentTimeMillis() / 1000;
				log(this.console, "[Finish Analyzing metFile: ]" + metPath + "at "
						+ Long.toString(afterTime) + ", spent "
						+ Long.toString(afterTime - beforeTime));
				sourceMetricMap.put(ma.getSourceFileName(), metricMap);
			}
			BufferedWriter writer = null;
			try {
				FileWriter fw = new FileWriter(STREAM_SUM_FOLDER
						+ File.separatorChar + this.streamName + ".csv");
				writer = new BufferedWriter(fw);
				writer.write("Module");
				writer.write(CSV_SEP);
				writer.write("Sub Module");
				writer.write(CSV_SEP);
				writer.write("File");
				writer.append(CSV_SEP);
				writer.write("STCYC");
				writer.append(CSV_SEP);
				writer.write("STDIT");
				writer.append(CSV_SEP);
				writer.write("STGTO");
				writer.append(CSV_SEP);
				writer.write("STLCM");
				writer.append(CSV_SEP);
				writer.write("STLIN");
				writer.append(CSV_SEP);
				writer.write("STMIF");
				writer.append(CSV_SEP);
				writer.write("STMTH");
				writer.append(CSV_SEP);
				writer.write("STNOC");
				writer.append(CSV_SEP);
				writer.write("STNOP");
				writer.append(CSV_SEP);
				writer.write("STOPT");
				writer.append(CSV_SEP);
				writer.write("STSUB");
				writer.append(CSV_SEP);
				writer.write("STTLN");
				writer.append(CSV_SEP);
				writer.write("STVAR");
				writer.append(CSV_SEP);
				writer.write("STWMC");
				writer.append(CSV_SEP);
				writer.write("STXLN");
				writer.append(System.getProperty("line.separator"));
				Set<String> filePaths = sourceMetricMap.keySet();
				for (String sourceFile : filePaths) {
					/* Write the module */
					int firstSlash = MetFileAnalyzer.getSubStrIndex(0, "\\",
							sourceFile);
					String module = firstSlash == -1 ? "" : sourceFile
							.substring(0, firstSlash);
					writer.write(module);
					writer.append(CSV_SEP);
					/* Write the sub module */
					int secondSlash = MetFileAnalyzer.getSubStrIndex(1, "\\",
							sourceFile);
					String subModule = secondSlash == -1 ? "" : sourceFile
							.substring(firstSlash + 1, secondSlash);
					writer.write(subModule);
					writer.append(CSV_SEP);
					writer.append(sourceFile);
					writer.append(CSV_SEP);
					writer.append(Long.toString(sourceMetricMap.get(sourceFile)
							.get("STCYC")));
					writer.append(CSV_SEP);
					writer.write(Long.toString(sourceMetricMap.get(sourceFile)
							.get("STDIT")));
					writer.append(CSV_SEP);
					writer.write(Long.toString(sourceMetricMap.get(sourceFile)
							.get("STGTO")));
					writer.append(CSV_SEP);
					writer.write(Long.toString(sourceMetricMap.get(sourceFile)
							.get("STLCM")));
					writer.append(CSV_SEP);
					writer.write(Long.toString(sourceMetricMap.get(sourceFile)
							.get("STLIN")));
					writer.append(CSV_SEP);
					writer.write(Long.toString(sourceMetricMap.get(sourceFile)
							.get("STMIF")));
					writer.append(CSV_SEP);
					writer.write(Long.toString(sourceMetricMap.get(sourceFile)
							.get("STMTH")));
					writer.append(CSV_SEP);
					writer.write(Long.toString(sourceMetricMap.get(sourceFile)
							.get("STNOC")));
					writer.append(CSV_SEP);
					writer.write(Long.toString(sourceMetricMap.get(sourceFile)
							.get("STNOP")));
					writer.append(CSV_SEP);
					writer.write(Long.toString(sourceMetricMap.get(sourceFile)
							.get("STOPT")));
					writer.append(CSV_SEP);
					writer.write(Long.toString(sourceMetricMap.get(sourceFile)
							.get("STSUB")));
					writer.append(CSV_SEP);
					writer.write(Long.toString(sourceMetricMap.get(sourceFile)
							.get("STTLN")));
					writer.append(CSV_SEP);
					writer.write(Long.toString(sourceMetricMap.get(sourceFile)
							.get("STVAR")));
					writer.append(CSV_SEP);
					writer.write(Long.toString(sourceMetricMap.get(sourceFile)
							.get("STWMC")));
					writer.append(CSV_SEP);
					writer.write(Long.toString(sourceMetricMap.get(sourceFile)
							.get("STXLN")));
					writer.append(System.getProperty("line.separator"));

				}
				writer.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

	}

	@Override
	public void summarize() {

	}

	/** so far it can only get the filelist from the QACPP GUI. */
	protected File getFileListFile() {
		File outputDir = this.getQACPPOutputDir();
		return new File(outputDir.getAbsolutePath() + File.separatorChar
				+ FILE_LIST_LST);
	}

	protected File getQACPPOutputDir() {
		File srcDir = new File(streamSrcPath);
		String parentDir = srcDir.getParent();
		File outputDir = new File(parentDir + File.separatorChar
				+ QACPP_OUT_FOLDER);
		if (!outputDir.exists()) {
			if (outputDir.mkdir())
				return outputDir;
		} else if (!outputDir.isDirectory()) {
			outputDir.delete();
		}
		return outputDir;
	}

	public List<?> getSummarizedMetrics() {
		return summarizedMetrics;
	}

	public Map<Object, List<?>> getDetailedMetrics() {
		return detailedMetrics;
	}

	/** get all .c, .cpp, and .h files from the source path */
	public List<File> getAllSourceFiles() {
		// TODO: get all source file from 'fileList.lst' or from storage based
		// on stream name
		return null;
	}

	@Override
	public boolean hasCrossModuleAnalysis() {
		return false;
	}

	private void log(Text console,String s) {
		if(console != null){
			if(console.isDisposed()){
				console.append(s);
				console.append("\n");
			}else{
				System.out.println("s");
			}
		}else{
			System.out.println("s");
		}
	}
}
